#include "diskTest.h"

#ifndef _DEBUG
#       define BUF_BASE_SIZE (1<<16)
#else // _DEBUG
#       define BUF_BASE_SIZE 256
#endif // _DEBUG

#define disk_read_err() do {                            \
        fprintf(stderr, "When %jd\n", buf_start);       \
        perr_e(_("Fail to read"));                      \
} while(0)

#define disk_write_err() do {                            \
        fprintf(stderr, "When %jd\n", ftello64(opt.fp)); \
        perr_e(_("Fail to write"));                      \
} while(0)

static char *buf = NULL;
static off_t buf_start = -1;
static size_t buf_available = 0;
static size_t buf_used = 0;
static bool buf_is_read = false;

#ifdef _GUI
#define GUI_STOP_CHK do {       \
        if (!gworking) {        \
                free(buf);      \
                thrd_exit(3);   \
        }                       \
} while (0)
#else // !_GUI
#define GUI_STOP_CHK
#endif // !_GUI

static void read_refresh() {
        buf_start += buf_used;
        buf_used = 0;
        if (0 != fseeko64(opt.fp, buf_start, SEEK_SET))
                disk_read_err();
        buf_available = fread(buf, 1, BUF_BASE_SIZE, opt.fp);
        if (buf_available == 0)
                disk_read_err();
}

void fbinit(off_t start, bool read) {
        buf = malloc_se(BUF_BASE_SIZE);
        buf_start = start;
        buf_is_read = read;
        if (read) {
                read_refresh();
        }
}

void fbread(void *bin, size_t size) {
        // if file size 2, means 0~1 can use.
        // `used` = 0, `size` = 2, `available` = 2
        // then it should be (0 + 2 > 2) == false
        GUI_STOP_CHK;
        if (buf_used + size > buf_available) {
                read_refresh();
                if (buf_available < size)
                        disk_read_err();
        }
        memcpy(bin, buf + buf_used, size);
        buf_used += size;
}

void fbwrite(void *bin, size_t size) {
        GUI_STOP_CHK;
        if (buf_used >= BUF_BASE_SIZE) {
                size_t fw_size = fwrite(buf, 1, buf_used, opt.fp);
                if (fw_size != buf_used)
                        disk_write_err();
                buf_used = 0;
                if (size >= BUF_BASE_SIZE) {
                        fprintf(stderr, _("Inner Err"));
                        raise(SIGSEGV);
                }
        }
        memcpy(buf + buf_used, bin, size);
        buf_used += size;
}

void fbflush() {
        GUI_STOP_CHK;
        if (buf_is_read)
                abort();
        if (buf_used == 0)
                return;
        size_t fw_size = fwrite(buf, 1, buf_used, opt.fp);
        if (fw_size != buf_used)
                disk_write_err();
        buf_used = 0;
}

#undef disk_err
